/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package percussiongenerator.gui;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.jfonia.connect5.basics.BasicValueNode;
import org.jfonia.connect5.basics.MutableValueNode;
import org.jfonia.connect5.basics.Observer;
import org.jfonia.connect5.intervals.Interval;
import org.jfonia.connect5.relations.Equal;
import org.jfonia.constants.ModelConstants;
import org.jfonia.constants.SymbolConstants;
import org.jfonia.model.PerformanceSequence;
import org.jfonia.model.TempoNode;
import org.jfonia.model.elements.TimeSignature;
import org.jfonia.model.elements.Tone;
import percussiongenerator.commands.CommandHistory;
import percussiongenerator.commands.Duplicate;
import percussiongenerator.commands.Stretch;
import percussiongenerator.model.Track;

/**
 * This panel is used to make changes to the length of the panel.
 * @author Jannes Plyson
 */
public class LengthPanel extends JPanel implements ChangeListener,Observer{
    Track track;
    JRadioButton btnDuplicate;
    JRadioButton btnStretch;
    JRadioButton btnDuplicateAndStretch;    
    TextFieldRelation txtBPM;
    TimeTextField timeTextField;
    JTextField txtStretchStep;
    MutableValueNode<Integer> type;
    MutableValueNode<Long> endNode;
    Equal<Long> equalRelation;
    RoundToMeasure roundToMeasure;
    MutableValueNode<Integer> bpmNode;
    JCheckBox chbStretchBPM;
    JCheckBox chbRoundToMeasure;

    /**
     * bpmNode: a value nodes that contains the bpm of the selected track.
     * This class listens to changes of bpmNode
     * txtBPM: textfield to change the value of the bpmNode
     * chbStretchBPM: when selected: length of track changes to new bpm,
     * else only the bpm changes and not the length of the track.
     * endNode: is connected with an equal relation to the endNode of
     * the endNode of the tone sequence.
     * roundToMeasure is an observer who listens to changes of the endNode.
     * roundToMeasure rounds the value of the endNode to the closest measure.
     * @param track The track to change
     */
    public LengthPanel(final Track track){
        this.track = track;
        this.setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));                        
        type = new BasicValueNode<Integer>(0);

        Box hbox = Box.createHorizontalBox();
        this.add(hbox);
        hbox.add(new JLabel("BPM:"));
        int bpm = ((TempoNode)track.getStaff().getTempoMap().getIntervalFromIndex(0)).getBPM();
        bpmNode = new BasicValueNode<Integer>(bpm);
        bpmNode.addObserver(this);
        txtBPM = new TextFieldRelation(bpmNode);
        txtBPM.setMaximumSize(new Dimension(50,20));
        txtBPM.setPreferredSize(new Dimension(50,20));
        hbox.add(txtBPM);
        chbStretchBPM = new JCheckBox("stretch");
        chbStretchBPM.setSelected(false);
        hbox.add(chbStretchBPM);
        hbox.add(Box.createHorizontalGlue());
        JLabel lblTimeSignature = new JLabel("Time signature");
        Box box = Box.createHorizontalBox();
        this.add(box);
        box.add(lblTimeSignature);
        Box vbox = Box.createVerticalBox();
        box.add(vbox);
        TimeSignature ts = (TimeSignature)track.getStaff().getSequence(TimeSignature.class).getMusicalElementFromIndex(0);
        TextFieldRelation txtNominator = new TextFieldRelation(ts.getNominatorNode());
        txtNominator.setMaximumSize(new Dimension(30,50));
        TextFieldRelation txtDenominator = new TextFieldRelation(ts.getDenominatorNode());
        txtDenominator.setMaximumSize(new Dimension(30,50));
        vbox.add(txtNominator);
        vbox.add(txtDenominator);
        box.add(Box.createHorizontalGlue());
        this.add(Box.createVerticalGlue());

        MutableValueNode<Long> perEnd = (track.getStaff().getToneSequence()).getPerformanceSequence().getEndNode();
        endNode = new BasicValueNode<Long>(perEnd.getValue());
        equalRelation = new Equal<Long>(perEnd, endNode);
        roundToMeasure = new RoundToMeasure(track);

        chbRoundToMeasure = new JCheckBox("Round to measure");        
        hbox = Box.createHorizontalBox();
        this.add(hbox);
        hbox.add(chbRoundToMeasure);
        hbox.add(Box.createHorizontalGlue());
        chbRoundToMeasure.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if(((JCheckBox)e.getSource()).isSelected()){
                    endNode.addObserver(roundToMeasure);                    
                    roundToMeasure.onNotify(endNode);
                }else{                    
                    endNode.removeObserver(roundToMeasure);
                }
            }
        });
        chbRoundToMeasure.setSelected(true);        

        hbox = Box.createHorizontalBox();
        this.add(hbox);
        hbox.add(new JLabel("length: "));
        timeTextField = new TimeTextField(track.getPerformanceLength());
        hbox.add(timeTextField);
        track.getStaff().getToneSequence().getPerformanceSequence().getEndNode().addObserver(this);
        timeTextField.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                long time = timeTextField.getNanoTime();
                CommandHistory.addCommandAndExecute(new Stretch(track.getStaff().getToneSequence().getPerformanceSequence(), time));
            }
        });        
        hbox.add(Box.createHorizontalGlue());        
        

        hbox = Box.createHorizontalBox();
        this.add(hbox);
        hbox.add(new JLabel("Stretch step: "));        
        txtStretchStep = new JTextField("1m");
        hbox.add(txtStretchStep);
        JButton btnStretchPositive = new JButton("+");
        hbox.add(btnStretchPositive);        
        JButton btnStretchNegative = new JButton("-");
        hbox.add(btnStretchNegative);
        btnStretchPositive.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                chbRoundToMeasure.setSelected(false);
                PerformanceSequence perSeq = track.getStaff().getToneSequence().getPerformanceSequence();
                CommandHistory.addCommandAndExecute(new Stretch(perSeq, perSeq.getDifference()+evaluateStretchStep()));
            }
        });
        btnStretchNegative.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                chbRoundToMeasure.setSelected(false);
                PerformanceSequence perSeq = track.getStaff().getToneSequence().getPerformanceSequence();
                CommandHistory.addCommandAndExecute(new Stretch(perSeq, perSeq.getDifference()-evaluateStretchStep()));
            }
        });
        hbox.add(Box.createHorizontalGlue());         

        hbox = Box.createHorizontalBox();
        this.add(hbox);
        hbox.add(new JLabel("Duplicates:"));
        JTextField txtDuplicate = new JTextField();
        txtDuplicate.setMaximumSize(new Dimension(50,25));
        txtDuplicate.setPreferredSize(new Dimension(50,25));
        hbox.add(txtDuplicate);
        hbox.add(Box.createHorizontalGlue());
        txtDuplicate.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try{
                    int numberOfDuplicates = Integer.parseInt(((JTextField)e.getSource()).getText());
                    boolean rounding = chbRoundToMeasure.isSelected();
                    chbRoundToMeasure.setSelected(false);                    
                    CommandHistory.addCommandAndExecute(new Duplicate(track.getStaff(), numberOfDuplicates));
                    chbRoundToMeasure.setSelected(rounding);
                }catch(Exception exc){}
            }
        });       

        this.add(Box.createVerticalGlue());       
    }

    public long evaluateStretchStep(){
        try{
            String text = txtStretchStep.getText();            
            Long step = 0l;
            if(text.endsWith("mic")){
                step = Long.parseLong(text.substring(0,text.length()-3))*1000l;
            }else if(text.endsWith("m")){
                step = Long.parseLong(text.substring(0,text.length()-1))*1000000l;
            }else if(text.endsWith("t") || text.endsWith("n")){
                int ticks = 1;
                if(text.endsWith("n")){
                    int noteValue = Integer.parseInt(text.substring(0,text.length()-1));
                    ticks = SymbolConstants.DIVISOR/noteValue;
                }else{
                    ticks = Integer.parseInt(text.substring(0,text.length()-1));
                }
                int bpm = ((TempoNode)track.getStaff().getTempoMap().getIntervalFromValue(ticks)).getBPM();
                int beatsize = ((TimeSignature)(track.getStaff().getSequence(TimeSignature.class).getIntervalFromValue(ticks))).getDenominator();
                int beatsizeTicks = SymbolConstants.DIVISOR/beatsize;
                step = (60l*ModelConstants.PERFORMANCE_TO_SECOND*ticks)/(bpm*beatsizeTicks);
            }else{
                step = Long.parseLong(text);
            }
            return step;
        }catch(Exception exc){}
        return 0;
    }

    public void close(){
        txtBPM.close();
        bpmNode.removeObserver(this);
        equalRelation.switchOff();
        track.getStaff().getToneSequence().getPerformanceSequence().getEndNode().removeObserver(this);
    }

    public void stateChanged(ChangeEvent e) {
        if(btnStretch.isSelected()){
            type.setValue(0);
        }else if(btnDuplicate.isSelected()){
            chbRoundToMeasure.setSelected(false);
            type.setValue(1);

        }else if(btnDuplicateAndStretch.isSelected()){
            chbRoundToMeasure.setSelected(false);
            type.setValue(2);
        }
    }

    public void onNotify(Object source) {
        if(source == track.getStaff().getToneSequence().getPerformanceSequence().getEndNode()){
            timeTextField.setTime(track.getPerformanceLength());
        }else{
            if(chbStretchBPM.isSelected()){
                endNode.removeObserver(roundToMeasure);
                int oldBPM = ((TempoNode)track.getStaff().getTempoMap().getIntervalFromIndex(0)).getBPMNode().getValue();
                double factor = (double)oldBPM / bpmNode.getValue();
                CommandHistory.addCommandAndExecute(new Stretch(track.getStaff().getToneSequence().getPerformanceSequence(), (long)(track.getPerformanceLength()*factor)));
            }
            ((TempoNode)track.getStaff().getTempoMap().getIntervalFromIndex(0)).getBPMNode().setValue((Integer)bpmNode.getValue());
            endNode.addObserver(roundToMeasure);
            roundToMeasure.onNotify(endNode);
        }
    }

    private class RoundToMeasure implements Observer{
        Track track;

        public RoundToMeasure(Track track){
            this.track = track;
        }

        private long getBeatWidth(){
            int bpm = ((TempoNode)track.getStaff().getTempoMap().getIntervalFromIndex(0)).getBPM();
            return (60l*ModelConstants.PERFORMANCE_TO_SECOND)/bpm;
        }

        private long getMeasureWidth(){
            TimeSignature ts = (TimeSignature)track.getStaff().getSequence(TimeSignature.class).getIntervalFromIndex(0);
            return getBeatWidth()*ts.getNominator();
        }

        public void onNotify(Object source) {
            PerformanceSequence perSeq = track.getStaff().getToneSequence().getPerformanceSequence();
            if(track.getPerformanceLength() % getMeasureWidth() != 0){                
                MutableValueNode<Long> endNode = perSeq.getEndNode();
                Interval<Long> val = perSeq.getIntervalFromIndex(perSeq.size()-1);
                if(val.getDifference() > track.getPerformanceLength() % getMeasureWidth()){                    
                    endNode.setValue(endNode.getValue() - track.getPerformanceLength() % getMeasureWidth());
                }else{
                    endNode.setValue(endNode.getValue() + (getMeasureWidth() - track.getPerformanceLength() % getMeasureWidth()));
                }
            }
        }
    }

}
